<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script>
 /* assert_array_equals() uses same_value() to compare each item, but
  * Object.entries() returns an array of arrays, so we need to compare
  * each item with assert_array_equals instead. */
 function assert_record_equals(actual, expected, description) {
   for (let i = 0; i < expected.length; ++i) {
     assert_array_equals(Object.entries(actual)[i], expected[i], description);
   }
 }

 test(() => {
   let recordTest = internals.recordTest();

   assert_throws_js(TypeError, () => {
     recordTest.setStringLongRecord(null);
   }, "Converting null to record should be rejected.");
   assert_throws_js(TypeError, () => {
     recordTest.setStringLongRecord(undefined);
   }, "Converting undefined to record should be rejected.");
   recordTest.setNullableStringLongRecord(null);
   assert_equals(recordTest.getNullableStringLongRecord(),
                 null, "Passing null to a nullable record works");
   recordTest.setNullableStringLongRecord(undefined);
   assert_equals(recordTest.getNullableStringLongRecord(),
                 null, "Passing undefined to a nullable record works");
 }, "Test handling of null and undefined records");

 test(() => {
   let recordTest = internals.recordTest();

   let record = {'foo': 42, 'quux': 34};
   let handler = {
     getOwnPropertyDescriptor: (target, name) => {
       if (name == 'quux')
         return undefined;
       return Reflect.getOwnPropertyDescriptor(target, name);
     }
   };
   var recordProxy = new Proxy(record, handler);
   recordTest.setStringLongRecord(record);
   assert_record_equals(
     recordTest.getStringLongRecord(), [['foo', 42]],
     "Entries whose getOwnPropertyDescriptor return undefined are skipped");
 }, "Test GetOwnPropertyDescriptor() returning undefined");

 test(() => {
   let recordTest = internals.recordTest();

   recordTest.setStringLongRecord({a: true, false: null, c: "foo"});
   assert_record_equals(recordTest.getStringLongRecord(),
                        [['a', 1], ['false', 0], ['c', 0]],
                        "Types are properly coerced to the record's types");
 }, "Test type conversion");

 test(() => {
   let recordTest = internals.recordTest();

   recordTest.setStringLongRecord({false: 1, [false]: 42});
   assert_record_equals(recordTest.getStringLongRecord(), [['false', 42]],
                        "Key types are coerced and never repeated");
 }, "Test duplicate keys");

 test(() => {
   let recordTest = internals.recordTest();

   recordTest.setStringLongRecord({z: 42, foo: -5, ABC: 0});
   assert_record_equals(recordTest.getStringLongRecord(),
                        [['z', 42], ['foo', -5], ['ABC', 0]],
                        "Keys are not sorted");
 }, "Test mapping order");

 test(() => {
   let recordTest = internals.recordTest();

   assert_throws_js(TypeError, () => {
     recordTest.setByteStringByteStringRecord({'a': 'bc', '\uFFFF': 'foo'});
   }, "Invalid ByteString key must throw a TypeError");
   assert_throws_js(TypeError, () => {
     recordTest.setByteStringByteStringRecord({'xy': 'z', 'foo': '\uFFFF'});
   }, "Invalid ByteString value must throw a TypeError");
 }, "Test ByteString validation");

 test(() => {
   let recordTest = internals.recordTest();

   recordTest.setStringLongRecord({'foo': 0});
   let record = recordTest.getStringLongRecord();
   assert_record_equals(record, [['foo', 0]]);

   record.baz = 'quux';
   assert_equals(Object.keys(recordTest.getStringLongRecord()).length, 1,
                 "Changing a returned record does not change the record");
   assert_record_equals(recordTest.getStringLongRecord(), [['foo', 0]],
                        "Changing a returned record does not change the record");

   assert_record_equals(recordTest.getStringLongRecord(),
                        Object.entries(recordTest.getStringLongRecord()),
                        "Record getters always return the same elements");
   assert_not_equals(recordTest.getStringLongRecord(), recordTest.getStringLongRecord(),
                     "Record getters always return a new copy");
 }, "Test records are passed by value");

 test(() => {
   let recordTest = internals.recordTest();

   assert_false(recordTest.unionReceivedARecord(true),
                "Passing 'true' should convert the union to boolean");
   assert_false(recordTest.unionReceivedARecord(false),
                "Passing 'false' should convert the union to boolean");
   assert_false(recordTest.unionReceivedARecord(42),
                "Passing a number should convert the union to boolean");
   assert_true(recordTest.unionReceivedARecord([1, 2, 3]),
               "Passing an array should convert the union to a record");
   assert_true(recordTest.unionReceivedARecord({}),
               "Passing an object should conver the union to a record");
 }, "Test unions resolve records");

 test(() => {
   let recordTest = internals.recordTest();

   let elem = document.createElement('p');
   recordTest.setStringElementRecord({'elem': elem});
   assert_equals(recordTest.getStringElementRecord().elem, elem,
                 "The same DOM object was stored in the record");
   assert_not_equals(recordTest.getStringElementRecord().elem,
                     document.createElement('p'),
                     "The same DOM object was stored in the record");

   elem = document.createElement('br');
   assert_not_equals(recordTest.getStringElementRecord().elem, elem,
                     "Changing the original value does not change the record value");
 }, "Test DOM object types");

 test(() => {
   let recordTest = internals.recordTest();
   let getterAlias = recordTest.getUSVStringUSVStringBooleanRecordRecord;

   recordTest.setUSVStringUSVStringBooleanRecordRecord({'foo': {'bar': true}, 'quux': {'baz': false}});
   let records = recordTest.getUSVStringUSVStringBooleanRecordRecord();
   assert_array_equals(Object.keys(records), ['foo', 'quux'],
                       "Nested record types have the correct keys");
   assert_record_equals(records.foo, [['bar', true]]);
   assert_record_equals(records.quux, [['baz', false]]);
 }, "Test nested record types");

 test(() => {
   let recordTest = internals.recordTest();

   let record = recordTest.returnStringByteStringSequenceRecord();
   assert_array_equals(Object.keys(record), ['foo', 'bar']);
   assert_array_equals(record.foo, ['hello, world', 'hi, mom']);
   assert_array_equals(record.bar, ['goodbye, mom']);

   let other_record = recordTest.returnStringByteStringSequenceRecord();
   assert_not_equals(record, other_record, "A new object is returned each time");
 }, "Test sequences in records");
</script>
